﻿using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Administration.Claims;
using MissingUserPermissions.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Xml.Serialization;

namespace MissingUserPermissions
{
    /// <summary>
    /// This tool currently gives you the ability to document user permission of your current farm. 
    /// 
    /// Currently only NTLM to Claims is supported 
    /// but will extend it to support other types of authentication
    /// </summary>
    class Program
    {

        public static Permissions PermissionMask = new Permissions();

        public static bool ConvertClaims;

        static void Main(string[] args)
        {
            try
            {

                Console.WriteLine("Identify missing User Permissions on Claims Conversion (developed by: daenet GmbH (Nadine Storandt))");
                Console.WriteLine("----------------------------------------------------------");
                Console.WriteLine("This tool gives the administrator the ability to check for any missing user permission after claims migration. ");

                Console.WriteLine("Following Options are available:");

                Console.WriteLine("1. Document User Permissions before Claims Migration");
                Console.WriteLine("2. Document User Permissions after Claims Migration");
                Console.WriteLine("3. Compare Permissions and add missing ones based on document input");
                Console.WriteLine("4. add Missing users");

                var option = Console.ReadLine();

                switch (option)
                {
                    case "1":
                        documentUserPermissionsBeforeClaims();
                        break;
                    case "2":
                        documentUserPermissionAfterClaims();
                        break;
                    case "3":
                        compareUserPermissions();
                        Console.WriteLine("Comparing done would you like to add the missing users to the groups? (yes/no)");

                        var answer = Console.ReadLine();
                        if (answer.ToLower() == "yes" || answer.ToLower() == "y")
                            addMissingUsers();

                        Console.WriteLine("Comparing done, please press any key to exit.");
                        Console.ReadLine();
                        break;
                    case "4":
                        addMissingUsers();
                        Console.WriteLine("Missing users has been added, please press any key to exit.");
                        Console.ReadLine();
                        break;
                    default:
                        throw new Exception("Not supported Option, please restart.");
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occurred please restart:");
                Console.WriteLine(ex.Message);
                Console.ReadLine();
            }

        }


        /// <summary>
        /// adds the missing users in depending on the compare result file 
        /// </summary>
        private static void addMissingUsers()
        {
            var missingPermissions = deserializeResult("CompareResults.xml");

            foreach (var site in missingPermissions.sites)
            {

                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPWeb s = new SPSite(site.Url).OpenWeb())
                    {
                        foreach (var grp in site.Groups)
                        {
                            try
                            {
                                var exGrp = s.Groups[grp.Name];

                                if (exGrp != null)
                                {
                                    foreach (var userName in grp.UserNames)
                                    {
                                        SPClaimProviderManager mgr = SPClaimProviderManager.Local;

                                        if (userName == "All Authenticated Users")
                                        {
                                            SPUser user = s.EnsureUser("c:0(.s|true");
                                            exGrp.AddUser(user);
                                        }
                                        else if (userName == "NT AUTHORITY\authenticated users")
                                        {
                                            SPUser user = s.EnsureUser("c:0!.s|true");
                                            exGrp.AddUser(user);
                                        }
                                        else
                                        {
                                            try
                                            {

                                                var loginName = mgr.ConvertIdentifierToClaim(userName, SPIdentifierTypes.WindowsSamAccountName);
                                                SPUser user = s.EnsureUser(loginName.Value);

                                                // Not really needed but for good coding added
                                                // If the user is already in the group then sharepoint will not add a duplicate
                                                if (!user.Groups.Cast<SPGroup>().Any(g => g.ID == exGrp.ID))
                                                    exGrp.AddUser(user);
                                              

                                            }
                                            catch (Exception ex)
                                            {
                                                if (ex.Message.Contains("The specified user"))
                                                {
                                                    Console.WriteLine(ex.Message);
                                                }
                                                else
                                                {
                                                    var loginName = mgr.ConvertIdentifierToClaim(userName, SPIdentifierTypes.WindowsSecurityGroupName);
                                                    SPUser user = s.EnsureUser(loginName.Value);
                                                    exGrp.AddUser(user);
                                                }


                                            }

                                        }

                                    }

                                }
                            }
                            catch (Exception ex)
                            {
                                if (ex.Message.ToLower().Contains("group cannot be found"))
                                    Console.WriteLine("Group '" + grp.Name + "' cannot be found for site '" + site.Url + "'");
                                else
                                    Console.WriteLine(ex.Message);
                            }

                        }
                    }
                });

            }
        }


        /// <summary>
        /// Documents the user permissions after claims conversion
        /// </summary>
        private static void documentUserPermissionAfterClaims()
        {
            ConvertClaims = true;

            try
            {
                Console.WriteLine("Following Options are available:");

                Console.WriteLine("1. Document whole Farm");
                Console.WriteLine("2. Document WebApplication");
                Console.WriteLine("3. Document SiteCollections within specified Database");
                Console.WriteLine("4. Document specific SiteCollection");

                var option = Console.ReadLine();

                switch (option)
                {
                    case "1":
                        documentWholeFarm();
                        break;
                    case "2":
                        Console.WriteLine("Please enter the Url for the webApp which should be documented:");

                        var webApp = Console.ReadLine();

                        if (String.IsNullOrEmpty(webApp))
                            throw new Exception("Please restart, no webApp url enterd.");

                        documentWebApplication(webApp);

                        break;
                    case "3":
                        Console.WriteLine("Please enter the name of the database which should be documented:");

                        var db = Console.ReadLine();

                        if (String.IsNullOrEmpty(db))
                            throw new Exception("Please restart, no db name enterd.");

                        documentDatabase(db);
                        break;
                    case "4":
                        Console.WriteLine("Please enter the Url for the sitecollection which should be documented:");

                        var siteCollection = Console.ReadLine();
                        documentSiteCollection(siteCollection);

                        break;
                    default:
                        throw new Exception("Not supported Option, please restart.");
                }

                serializeResults("PermissionAfterClaims.xml");


                Console.WriteLine("Execution done! Press any key to exit ... ");
                Console.ReadLine();

            }
            catch (Exception ex)
            {
                throw ex;
            }

        }


        /// <summary>
        /// Comparing the two input files to missing user names
        /// </summary>
        private static void compareUserPermissions()
        {
            var permissionAfterClaims = deserializeResult("PermissionAfterClaims.xml");
            var permissionBeforeClaims = deserializeResult("PermissionBeforeClaims.xml");

            // Special case there is  a site dmc2011 which is renamed but has the same SiteId
            bool renamedSite = false;

            Permissions permissionsToAdd = new Permissions();

            foreach (Site site in permissionBeforeClaims.sites)
            {

                foreach (Group grp in site.Groups)
                {
                    try
                    {
                        var afterSite = permissionAfterClaims.sites.Where(s => s.SiteId == site.SiteId).FirstOrDefault();

                        if (afterSite != null)
                        {
                            var afterGrp = afterSite.Groups.Where(g => g.Name.ToLower() == grp.Name.ToLower()).FirstOrDefault();

                            if (afterGrp == null)
                            {
                                // special case site has same siteId like other one 
                                if (site.Url.ToLower() != afterSite.Url.ToLower())
                                {
                                    afterSite = permissionAfterClaims.sites.Where(s => s.Url == site.Url).FirstOrDefault();
                                    afterGrp = afterSite.Groups.Where(g => g.Name.ToLower() == grp.Name.ToLower()).FirstOrDefault();
                                }

                                if (afterSite.Url.ToLower() != site.Url.ToLower())
                                {
                                    Console.WriteLine("site has been renamed " + afterSite.Url);
                                    renamedSite = true;

                                    var siteTitleAfter = afterSite.Url.Substring(afterSite.Url.LastIndexOf("/") + 1);
                                    var siteTitleBefore = site.Url.Substring(site.Url.LastIndexOf("/") + 1);

                                    afterGrp = afterSite.Groups.Where(g => g.Name.ToLower() == grp.Name.ToLower().Replace(siteTitleBefore, siteTitleAfter)).FirstOrDefault();
                                }
                            }


                            if (afterGrp == null && !renamedSite)
                            {
                                if (permissionsToAdd.sites == null)
                                    permissionsToAdd.sites = new List<Site>();

                                if (permissionsToAdd.sites.Where(s => s.SiteId == site.SiteId).FirstOrDefault() == null)
                                {
                                    Site newSite = new Site();
                                    newSite.SiteId = site.SiteId;
                                    newSite.Url = site.Url;
                                    newSite.Groups = new List<Group>();
                                    newSite.Groups.Add(grp);

                                    permissionsToAdd.sites.Add(newSite);

                                }
                                else
                                {
                                    var exSite = permissionsToAdd.sites.Where(s => s.SiteId == site.SiteId).FirstOrDefault();
                                    exSite.Groups.Add(grp);
                                }
                            }

                            Group newGrp = new Group();

                            if (renamedSite)
                                newGrp.Name = afterGrp.Name;
                            else
                                newGrp.Name = grp.Name;

                            newGrp.UserNames = new List<string>();

                            foreach (var user in grp.UserNames)
                            {
                                if (afterGrp != null && !afterGrp.UserNames.Contains(user, StringComparer.InvariantCultureIgnoreCase))
                                {
                                    newGrp.UserNames.Add(user);
                                }
                                else if (afterGrp == null)
                                {
                                    newGrp.UserNames.Add(user);
                                
                                }
                            }


                            if (newGrp.UserNames.Count > 0)
                            {
                                if (permissionsToAdd.sites == null)
                                    permissionsToAdd.sites = new List<Site>();

                                if (permissionsToAdd.sites.Where(s => s.SiteId == site.SiteId).FirstOrDefault() == null)
                                {
                                    Site newSite = new Site();
                                    newSite.SiteId = site.SiteId;
                                    newSite.Url = site.Url;
                                    newSite.Groups = new List<Group>();
                                    newSite.Groups.Add(newGrp);

                                    permissionsToAdd.sites.Add(newSite);

                                }
                                else
                                {
                                    var exSite = permissionsToAdd.sites.Where(s => s.SiteId == site.SiteId).FirstOrDefault();
                                    exSite.Groups.Add(newGrp);
                                }

                            }

                        }

                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message + ex.StackTrace);
                    }
                }

            }

            PermissionMask = permissionsToAdd;
            serializeResults("CompareResults.xml");
        }


        /// <summary>
        /// documents user permissions before claims conversion 
        /// 
        /// Currently only NTLM supported
        /// </summary>
        private static void documentUserPermissionsBeforeClaims()
        {
            try
            {
                Console.WriteLine("Following Options are available:");

                Console.WriteLine("1. Document whole Farm");
                Console.WriteLine("2. Document WebApplication");
                Console.WriteLine("3. Document SiteCollections within specified Database");
                Console.WriteLine("4. Document specific SiteCollection");

                var option = Console.ReadLine();

                switch (option)
                {
                    case "1":
                        documentWholeFarm();
                        break;
                    case "2":
                        Console.WriteLine("Please enter the Url for the webApp which should be documented:");

                        var webApp = Console.ReadLine();

                        if (String.IsNullOrEmpty(webApp))
                            throw new Exception("Please restart, no webApp url enterd.");

                        documentWebApplication(webApp);

                        break;
                    case "3":
                        Console.WriteLine("Please enter the name of the database which should be documented:");

                        var db = Console.ReadLine();

                        if (String.IsNullOrEmpty(db))
                            throw new Exception("Please restart, no db name enterd.");

                        documentDatabase(db);
                        break;
                    case "4":
                        Console.WriteLine("Please enter the Url for the sitecollection which should be documented:");

                        var siteCollection = Console.ReadLine();

                        if (String.IsNullOrEmpty(siteCollection))
                            throw new Exception("Please restart, no sitecollection url enterd.");
                        
                            documentSiteCollection(siteCollection);

                        break;
                    default:
                        throw new Exception("Not supported Option, please restart.");
                }

                serializeResults("PermissionBeforeClaims.xml");


                Console.WriteLine("Execution done! Press any key to exit ... ");
                Console.ReadLine();

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        #region result serialize or deserialize

        /// <summary>
        /// Serialize the results of type Permission to a xml file
        /// </summary>
        /// <param name="fileName"></param>
        private static void serializeResults(string fileName)
        {
            using (var writer = new System.IO.StreamWriter(fileName, false))
            {
                var serializer = new XmlSerializer(PermissionMask.GetType());
                serializer.Serialize(writer, PermissionMask);
                writer.Flush();
            }

        }

        /// <summary>
        /// Deserialize the results of type Permission from file to object
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private static Permissions deserializeResult(string fileName)
        {
            Permissions result = new Permissions();
            using (var reader = new System.IO.StreamReader(fileName))
            {
                var serializer = new XmlSerializer(PermissionMask.GetType());
                result = (Permissions)serializer.Deserialize(reader);

            }

            return result;
        }
        #endregion


        #region documenting

        /// <summary>
        /// documents all groups with Usersnames
        /// </summary>
        /// <param name="web"></param>
        /// <returns></returns>
        private static List<Group> getAllGroupsWithUsersFromWeb(SPWeb web)
        {
            List<Group> result = new List<Group>();


            foreach (SPGroup group in web.Groups)
            {
                Group curGroup = new Group();

                curGroup.Name = group.Name;
                curGroup.UserNames = new List<string>();

                foreach (SPUser user in group.Users)
                {
                    if (ConvertClaims)
                    {

                        if (user.LoginName.Contains("\\"))
                        {
                            SPClaimProviderManager mgr = SPClaimProviderManager.Local;
                            if (mgr != null && !user.LoginName.ToLower().StartsWith("sharepoint\\"))
                            {
                                try
                                {
                                    curGroup.UserNames.Add(mgr.DecodeClaim(user.LoginName).Value);
                                }
                                catch (Exception)
                                {
                                    curGroup.UserNames.Add(user.LoginName);
                                }
                            }
                            else if (user.LoginName.ToLower().StartsWith("sharepoint\\"))
                                curGroup.UserNames.Add(user.LoginName);
                        }
                        else
                        {

                            SPClaimProviderManager mgr = SPClaimProviderManager.Local;

                            if (user.LoginName == "c:0(.s|true")
                            {
                                curGroup.UserNames.Add("All Authenticated Users");
                            }
                            else if (user.LoginName == "c:0!.s|true")
                            {
                                curGroup.UserNames.Add("NT AUTHORITY\authenticated users");
                            }
                            else
                            {

                                try
                                {
                                    var loginName = mgr.ConvertClaimToIdentifier(user.LoginName);
                                    curGroup.UserNames.Add(loginName);

                                }
                                catch (Exception)
                                {
                                    Console.WriteLine("Can not convert user or group with loginname - " + user.LoginName);

                                }

                            }
                        }

                    }
                    else
                        curGroup.UserNames.Add(user.LoginName);
                }

                result.Add(curGroup);
            }

            return result;
        }

        /// <summary>
        /// document the specified siteCollection
        /// </summary>
        /// <param name="siteCollection">The url to the siteCollection</param>
        private static void documentSiteCollection(string siteCollection)
        {
            try
            {

                if (!String.IsNullOrEmpty(siteCollection))
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        using (SPSite site = new SPSite(siteCollection))
                        {

                            if (site == null)
                                throw new Exception("Site can not be found. Please ensure this site exists");


                            if (PermissionMask.sites == null)
                                PermissionMask.sites = new List<Site>();

                            recursive(site.RootWeb);
                        }
                    });
                }
                else
                    throw new Exception("no siteCollection url entered. Please restart.");

            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        /// <summary>
        /// Gets all SubWebs recursive for a certain siteCollection
        /// </summary>
        /// <param name="web">The actual SPWeb</param>
        private static void recursive(SPWeb web)
        {
            var curSite = new Site();

            curSite.SiteId = web.ID;
            curSite.Url = web.Url;

            curSite.Groups = getAllGroupsWithUsersFromWeb(web);

            PermissionMask.sites.Add(curSite);

            if (web.Webs.Count > 0)
            {
                foreach (SPWeb oweb in web.Webs)
                {
                    recursive(oweb);
                }
            }

            if (web != null)
                web.Dispose();
        }

        /// <summary>
        /// documents a certain database
        /// </summary>
        /// <param name="dbName">The db which should be documented</param>
        private static void documentDatabase(string dbName)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPWebServiceCollection webServices = new SPWebServiceCollection(SPFarm.Local);
                foreach (SPWebService webService in webServices)
                {
                    foreach (SPWebApplication wa in webService.WebApplications)
                    {
                        foreach (SPContentDatabase db in wa.ContentDatabases)
                        {

                            if (db.Name == dbName)
                            {
                                foreach (SPSite site in db.Sites)
                                {
                                    documentSiteCollection(site.Url);
                                }
                            }
                        }

                    }
                }
            });
        }

        /// <summary>
        /// documents a certain WebApplication from the Farm 
        /// </summary>
        /// <param name="webAppUrl">the actual WebApplication url</param>
        private static void documentWebApplication(string webAppUrl)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPWebApplication webApp = SPWebApplication.Lookup(new Uri(webAppUrl));

                foreach (SPSite site in webApp.Sites)
                {
                    documentSiteCollection(site.Url);
                }
            });
        }


        /// <summary>
        /// documents the whole Farm
        /// which means, all available Webapplications
        /// </summary>
        private static void documentWholeFarm()
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPWebServiceCollection webServices = new SPWebServiceCollection(SPFarm.Local);
                foreach (SPWebService webService in webServices)
                {
                    foreach (SPWebApplication wa in webService.WebApplications)
                    {
                        documentWebApplication(wa.GetResponseUri(SPUrlZone.Default).AbsoluteUri);
                    }
                }
            });
        }
        #endregion
    }
}
